﻿//-----------------------------------------------------------------------
// <copyright file="ExplorerViewModel.cs" author="Artak Mkrtchyan">
//     Creative Commons - GNU General Public License (http://creativecommons.org/licenses/GPL/2.0/
// </copyright>
// <author>Artak Mkrtchyan</author>
// <email>mkArtak@gmail.com</email>
// <date>15/07/2012</date>
//-----------------------------------------------------------------------
using ConsoleHoster.Common.UserInput;
using ConsoleHoster.Common.Utilities;
using ConsoleHoster.Common.Utilities.IO;
using ConsoleHoster.Common.Utilities.Threading;
using ConsoleHoster.Common.ViewModel;
using ConsoleHoster.Model.Entities;
using ConsoleHoster.ViewModel.Entities;
using System;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Input;

namespace ConsoleHoster.ViewModel
{
	public delegate void ExplorerItemEventHandler(ExplorerViewModel argSender, string argItemPath);

	public class ExplorerViewModel : ViewModelBase, IStateRestorable, IExplorerViewModel
	{
		public const string MOUSE_INPUT_CONTEXT_RIGHT_BUTTON_UP = "MouseRightButtonUp";

		private readonly ObservableCollection<ExplorerItemViewModel> drives = new ObservableCollection<ExplorerItemViewModel>();
		private bool autoCollapseOldItems = false;
		private ExplorerItemViewModel selectedItem;
		private readonly IConsolePluginApi consoleVM;
		private string folderToNavigateTo = null;
		private bool stateSaved = false;
		private readonly IKeyboardStateProvider keyboardStateProvider;
		private readonly IFileSystemContentProvider directoryContentProvider;

		public ExplorerViewModel(
			IConsolePluginApi argConsoleVM,
			IKeyboardStateProvider argKeyboardStateProvider,
			IFileSystemContentProvider argDirectoryContentProvider,
			IDispatcher argDispatcher,
			ILogger argLogger)
			: base(argDispatcher, new ComponentLogger("ExplorerViewModel", argLogger))
		{
			ValidationManager.RequireArgumentNotNull(argKeyboardStateProvider, "argKeyboardStateProvider");
			ValidationManager.RequireArgumentNotNull(argDirectoryContentProvider, "argDirectoryContentProvider");

			this.directoryContentProvider = argDirectoryContentProvider;
			this.consoleVM = argConsoleVM;
			this.keyboardStateProvider = argKeyboardStateProvider;
		}

		#region IStateRestorable implementation
		public void SaveState()
		{
			this.RunActionWithErrorHandling(() =>
				{
					if (this.selectedItem != null)
					{
						this.FolderToNavigateTo = this.selectedItem.ExplorerItem.Path;
					}

					ObservableCollection<ExplorerItemViewModel> tmpDrives = this.Drives;
					if (tmpDrives != null)
					{
						// Clearing the list of drives in the explorer tree when navigating away from current tab.
						// This is done to avoid performance hit by loading explorer tree (sometimes there are thousands of items loaded) when navigating back.
						tmpDrives.Clear();
					}
					this.StateSaved = true;
				});
		}

		public async void RestoreState()
		{
			if (this.StateSaved)
			{
				await this.RetrieveDrivesAsync(false);
			}
		}
		#endregion

		public async Task RetreieveDrives()
		{
			await this.RunAsyncAction(async () =>
			{
				await this.RetrieveDrivesAsync(true);
			}, true, "Loading drives...", true);
		}

		public void OnItemChosen(ExplorerItem argItem, bool argUseFullName)
		{
			this.ConsoleVM.AppendCommand(argUseFullName ? argItem.Path : argItem.Name);
		}

		public void NavigateTo(string argFolder)
		{
			this.Logger.LogMessage("Navigating to folder: '{0}' in explorer", argFolder);
			if (!String.IsNullOrWhiteSpace(argFolder))
			{
				string[] tmpPathParts = argFolder.Split('\\');
				string tmpDriveToNavigateTo = tmpPathParts[0] + "\\";

				if (this.Drives.Any())
				{
					ExplorerItemViewModel tmpCurrentItem = this.Drives.Where(item => String.Equals(item.ExplorerItem.Name, tmpDriveToNavigateTo, StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault();
					if (tmpCurrentItem != null)
					{
						tmpCurrentItem.NavigateTo(argFolder);
					}
					else
					{
						this.FolderToNavigateTo = argFolder;
					}
				}
				else
				{
					this.FolderToNavigateTo = argFolder;
				}
			}
		}

		internal async Task RetrieveDrivesAsync(bool argForceRefresh)
		{
			if (argForceRefresh)
			{
				await this.RunActionOnUIThread(() => this.Drives.Clear());
			}

			if (!this.Drives.Any())
			{
				foreach (DriveInfo tmpDriveInfo in this.directoryContentProvider.GetAvailableDrives())
				{
					ExplorerItem tmpItem = new ExplorerItem(tmpDriveInfo);
					await this.RunActionOnUIThread(() =>
						{
							ExplorerItemViewModel itemVM = new ExplorerItemViewModel(
								tmpItem,
								item =>
									this.OnSelectedItemChanged(this.selectedItem, item),
									this.ConsoleVM,
									this.keyboardStateProvider,
									this.Dispatcher,
									new ProcessManager(),
									this.directoryContentProvider,
									this.Logger);
							this.Drives.Add(itemVM);

							if (this.ShouldNavigateToDrive(tmpDriveInfo))
							{
								itemVM.NavigateTo(this.FolderToNavigateTo);
								this.FolderToNavigateTo = null;
							}
						});
				}
			}
		}

		private bool ShouldNavigateToDrive(DriveInfo argDriveInfo)
		{
			return this.FolderToNavigateTo != null && this.FolderToNavigateTo.StartsWith(argDriveInfo.Name);
		}

		private void OnSelectedItemChanged(ExplorerItemViewModel argOldItem, ExplorerItemViewModel argNewItem)
		{
			this.selectedItem = argNewItem;

			if (this.AutoCollapseOldItems && argNewItem != null)
			{
				if (argOldItem != null && argOldItem != argNewItem && !argOldItem.IsParentOf(argNewItem))
				{
					ExplorerItemViewModel tmpItemToCollapse = argOldItem;
					while (tmpItemToCollapse != null && !tmpItemToCollapse.IsParentOf(argNewItem))
					{
						tmpItemToCollapse.Collapse();
						tmpItemToCollapse = tmpItemToCollapse.Parent;
					}
				}
			}
		}

		#region Properties
		public ICommand OpenExplorerCommand
		{
			get
			{
				return null;
			}
		}

		public ObservableCollection<ExplorerItemViewModel> Drives
		{
			get
			{
				return this.drives;
			}
		}

		public bool AutoCollapseOldItems
		{
			get
			{
				return this.autoCollapseOldItems;
			}
			set
			{
				this.autoCollapseOldItems = value;
			}
		}

		public IConsolePluginApi ConsoleVM
		{
			get
			{
				return this.consoleVM;
			}
		}

		public string FolderToNavigateTo
		{
			get
			{
				return this.folderToNavigateTo;
			}
			set
			{
				this.folderToNavigateTo = value;
			}
		}

		private bool StateSaved
		{
			get
			{
				return this.stateSaved;
			}
			set
			{
				this.stateSaved = value;
			}
		}
		#endregion
	}
}